# -*- coding: utf-8 -*-

import os

from tornado.escape import xhtml_escape
from tornado.web import RequestHandler, url, authenticated, StaticFileHandler, stream_request_body

from bopress import coreplugins
from bopress import options, user
from bopress import settings
from bopress import taxonomy as term_taxonomy
from bopress.cache import Cache
from bopress.forms import FormData, UserRegistryFrom, UserLoginFrom, TermTaxonomyForm
from bopress.hook import do_action, apply_filters, AdminMenus, Hooks
from bopress.log import Logger
from bopress.mail import EMailQueue
from bopress.model import Users, Attachment
from bopress.orm import SessionFactory, pk
from bopress.ui import Screen
from bopress.user import Auth
from bopress.utils import Utils

__author__ = 'yezang'


class BaseHandler(RequestHandler):
    def head_json(self):
        self.set_header("Content-Type", "text/json;charset=utf-8")

    def get_template_namespace(self):
        """Returns a dictionary to be used as the default template namespace.

        May be overridden by subclasses to add or modify values.

        The results of this method will be combined with additional
        defaults in the `tornado.template` module and keyword arguments
        to `render` or `render_string`.
        """
        namespace = dict(
            handler=self,
            request=self.request,
            current_user=self.current_user,
            locale=self.locale,
            _=self.locale.translate,
            pgettext=self.locale.pgettext,
            static_url=self.static_url,
            xsrf_form_html=self.xsrf_form_html,
            reverse_url=self.reverse_url,
            # Custom
            site_name=self.get_site_name,
            current_year=self.get_current_year,
            nickname=self.get_current_user_nickname,
            auth=self.auth,
            session=self.session,
            do_action=do_action,
            apply_filters=apply_filters,
            api=self.api_url,
            admin_url=self.admin_url,
            plugin_static_url=self.plugin_static_url
        )
        namespace.update(self.ui)
        return namespace

    def xsrf_form_html(self):
        if settings.XSRF_COOKIES:
            return '<input type="hidden" name="_xsrf" value="' + \
                   xhtml_escape(self.xsrf_token) + '"/>'
        return ''

    @property
    def get_site_name(self):
        site_opts = options.get_site_options()
        return site_opts.get("site_name", default="BoPress")

    @property
    def get_current_year(self):
        return Utils.current_datetime().year

    def api_url(self, *args):
        return super(BaseHandler, self).reverse_url("api", *args)

    def admin_url(self, *args):
        return super(BaseHandler, self).reverse_url("admin", *args)

    def plugin_static_url(self, path, version="", include_host=None):
        """
        获取在plugins目录内的静态文件的url
        :param path: 文件的相对路径，例如 plugins/a/b/abc.min.js，则相对路径为 a/b/abc.min.js
        :param version: 静态文件版本，便于浏览器清除缓存
        :param include_host: 是否包含域名
        :return: 静态资源url
        """
        if include_host is None:
            include_host = False

        if include_host:
            base = self.request.protocol + "://" + self.request.host
        else:
            base = ""

        c = path.split("/")
        new_path = "/".join([x for x in c if x != ''])
        static_paths = Hooks.static_paths()
        for static_path in static_paths:
            if new_path.startswith(static_path):
                url = base + "/bopress/plugin/static/{0}{1}" \
                    .format(static_paths[static_path], new_path.replace(static_path, ""))
                if version:
                    url += "?v={0}".format(version)
                return url
        return ""

    def render_json(self, data=None, msg="", success=True):
        t = dict()
        t["message"] = msg
        t["success"] = success
        if data:
            t["data"] = data
        else:
            t["data"] = ""
        self.head_json()
        self.write(Utils.encode(t))

    def render_datatables(self, dataset=None, records_total=0, records_filtered=0, draw=1):
        """
        为JQuery DataTables 返回JSON数据
        :param draw:
        :param dataset: 表数据
        :param records_total: 总记录数
        :param records_filtered: 总过滤记录数
        """
        if not dataset:
            dataset = list()
        r = dict()
        r["draw"] = draw
        r["recordsTotal"] = records_total
        r["recordsFiltered"] = records_filtered
        r["data"] = dataset
        self.head_json()
        self.write(Utils.encode(r))

    # def render_html(self, file_name, *args, **kwargs):
    #     self.application.settings.get("template_path")

    def auth(self, caps=set(), is_redirect=False, is_api=True):
        """
        Http 权限认证
        :param caps: 权限集合 str or list or set
        :param is_redirect: 认证失败是否重定向
        :param is_api: 认证失败以JSON返回未认证错误信息
        :return: 认证是否成功
        """
        caps_param_type = type(caps)
        if caps_param_type is str:
            caps = {caps}
        elif caps_param_type is list:
            caps = set(caps)
        if self.is_super():
            return True
        points = self.session("bo_caps")
        if not points:
            a = Auth(user_id=self.get_current_user())
            points = a.get_perms()
            self.session("bo_caps", points)
        if caps.issubset(points):
            return True
        else:
            if is_redirect:
                self.redirect(self.get_login_url())
            elif is_api:
                self.render_json("403", "UnAuth", False)
            else:
                return False

    def get_login_url(self):
        return self.reverse_url("login")

    def get_current_user(self):
        """
        user id, int
        :return: user id, int type
        """
        id_ = self.get_secure_cookie("bo_current_user")
        if id_:
            return id_.decode('utf-8')
        return None

    @property
    def get_current_user_nickname(self):
        """
        current account user_login field.
        :return:
        """
        name = self.get_secure_cookie("bo_current_user_nickname")
        if name:
            return name.decode('utf-8')
        return ""

    def is_super(self):
        s = self.session("is_bo_super")
        if s:
            return s
        else:
            a = Auth(user_id=self.get_current_user())
            s = a.is_super()
            self.session("is_bo_super", s)
        return s

    def session(self, name, value=None):
        """
        get or set session value.
        get: self.session('name')
        set: self.session('name', v)
        :param name:
        :param value:
        :return:
        """
        if not self.current_user:
            return None
        if value:
            Cache.session().set(["%s" % self.current_user, name], value, settings.SESSION_TIMEOUT)
        else:
            return Cache.session().get(["%s" % self.current_user, name])

    def clear_session(self):
        """
        clear session
        """
        if self.current_user:
            Cache.session().delete(["%s" % self.current_user])

    def data_received(self, chunk):
        pass


class ApiHandler(BaseHandler):
    def get(self, action):
        """

        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_get", self, action)

    def post(self, action):
        """

        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_post", self, action)

    def put(self, action):
        """

        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_put", self, action)

    def delete(self, action):
        """

        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_delete", self, action)

    def head(self, action):
        """
        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_head", self, action)

    def options(self, action):
        """
        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_options", self, action)

    def patch(self, action):
        """
        :param action: \w+ 字母、数字、下划线
        """
        do_action("bo_api_patch", self, action)


class TaxonomyTreeHandler(BaseHandler):
    def post(self, *args, **kwargs):
        if not self.current_user:
            self.render_json("", "403", False)
        taxonomy = self.get_argument("taxonomy", None)
        action = self.get_argument("action", None)
        if not taxonomy or not action:
            self.render_json("", "500", False)
        else:
            # taxonomy 是否合法
            e = term_taxonomy.term_taxonomy_exists(taxonomy)
            if not e:
                self.render_json("", "404", False)
            else:
                tt_key = "bo_termtaxonomy_{0}".format(taxonomy)
                caps = options.get_options(tt_key, set())
                if self.auth(caps):
                    if action == "get":
                        term_taxonomy_id = self.get_argument('term_taxonomy_id', '')
                        if term_taxonomy_id:
                            term_taxonomy_item = term_taxonomy.get_term_taxonomys(taxonomy, term_taxonomy_id)
                            self.render_json(term_taxonomy_item, '200', True)
                        else:
                            self.render_json("", "404", False)
                    elif action == "create":
                        tt_form = TermTaxonomyForm(FormData(self))
                        b, v = term_taxonomy.create_termtaxonomy(tt_form.name.data, tt_form.slug.data, taxonomy,
                                                                 tt_form.parent_id.data, tt_form.description.data,
                                                                 tt_form.position.data)
                        if b:
                            rs = term_taxonomy.get_term_taxonomys(taxonomy, v.term_taxonomy_id)
                            self.render_json(rs, "200", success=True)
                        else:
                            self.render_json(success=False)
                    elif action == "update":
                        tt_form = TermTaxonomyForm(FormData(self))
                        b, msg, v = term_taxonomy.update_termtaxonomy(tt_form.term_taxonomy_id.data,
                                                                      tt_form.name.data, tt_form.slug.data, taxonomy,
                                                                      tt_form.parent_id.data, tt_form.description.data,
                                                                      tt_form.position.data)
                        if b:
                            rs = term_taxonomy.get_term_taxonomys(taxonomy, v.term_taxonomy_id)
                            self.render_json(rs, "200", success=True)
                        else:
                            self.render_json(msg, success=False)
                    elif action == "delete":
                        term_taxonomy_id = self.get_argument('term_taxonomy_id', '')
                        if term_taxonomy_id:
                            default_set = term_taxonomy.get_default_term_taxonomy_idset(taxonomy)
                            unnamed_id = term_taxonomy.get_unnamed_term_taxonomy_id(taxonomy)
                            if unnamed_id:
                                default_set.add(unnamed_id)
                            if term_taxonomy_id not in default_set:
                                b, msg = term_taxonomy.delete_taxonomy(term_taxonomy_id)
                                self.render_json('', msg, b)
                            else:
                                self.render_json("", "locked", False)
                        else:
                            self.render_json("", "404", False)
                    else:
                        self.render_json("", "500", False)


class ListTableDataHandler(BaseHandler):
    """为ListTable提供AJAX数据"""

    def get(self, table_id):
        table = Hooks.ui_list_tables().get(table_id, None)
        if table:
            if table.require_auth():
                if not self.current_user:
                    self.render_datatables([])
                else:
                    table.data(self, Utils.decode(self.get_query_argument("params")))
            else:
                table.data(self, Utils.decode(self.get_query_argument("params")))
        else:
            self.render_datatables([])

    def post(self, table_id):
        table = Hooks.ui_list_tables().get(table_id, None)
        behavior = self.get_argument("bo_listtable_behavior", None)
        if table and behavior:
            if table.require_auth():
                if not self.current_user:
                    self.render_json(success=False, msg="403", data="")
                else:
                    self._do_data(behavior, table)
            else:
                self._do_data(behavior, table)
        else:
            self.render_json(success=False, data="")

    def _do_data(self, behavior, table):
        if behavior == "do-action":
            action_name = self.get_argument("action_name", "")
            table.do_actions(action_name, self)
        elif behavior == "do-data-save":
            table.do_data_save(self)


class AdminHandler(BaseHandler):
    @authenticated
    def get(self):
        do_action("admin_init", self)
        screen_id = self.get_query_argument("page", "")
        if not screen_id:
            screen_id = "bo-dashboard"
        menu_item = AdminMenus.get_menu(screen_id)
        if menu_item:
            caps = menu_item["capability"]
            if self.auth(set(caps), True, False):
                current_screen = Screen(screen_id, self)
                current_screen.current_menu = menu_item
                cb = menu_item["cb"]
                if type(cb) is str:
                    self.render(cb, current_screen=current_screen)
                else:
                    cb(current_screen)
        else:
            self.write("Page Not Found.")


class IndexHandler(BaseHandler):
    def get(self):
        self.redirect(self.get_login_url())


class RegistryHandler(BaseHandler):
    def get(self):
        self.render("bocore/admin/register.html")

    def post(self, *args, **kwargs):
        site_options = options.get_site_options()
        if site_options.get("users_can_register", default='Y') != "Y":
            self.render_json("", success=False, msg="系统已关闭新用户注册")
        else:
            f = UserRegistryFrom(FormData(self))
            if f.validate():
                r = user.registry(f.user_email.data, f.user_pass.data, f.user_email.data)
                if not r.success:
                    self.head_json()
                    self.write(r.to_dict())
                else:
                    u = r.data
                    try:
                        link = "%s%s?ak=%s" % (settings.DOMAIN,
                                               self.reverse_url("verify"), u.user_activation_key)
                        title = u"新用户注册激活"
                        body = u"""
                        <html><body>点此链接激活账户: <a href="%s">%s</a></body></html>
                        """ % (link, link)
                        EMailQueue.send([u.user_email], title, body)
                    except Exception as e:
                        Logger.exception(e)
                    self.render_json()
            else:
                self.render_json(f.errors, success=False, msg="")


class LoginHandler(BaseHandler):
    def get(self):
        self.render("bocore/admin/login.html")

    def post(self, *args, **kwargs):
        f = UserLoginFrom(FormData(self))
        if f.validate():
            r = user.login(f.user_email.data, f.user_pass.data)
            if r.success:
                u = r.data
                nick_name = u.display_name
                if not nick_name:
                    nick_name = u.user_login
                self.set_secure_cookie("bo_current_user", "%s" % u.user_id)
                self.set_secure_cookie("bo_current_user_nickname", nick_name)
                self.render_json({"next": self.get_query_argument("next", "")})
            else:
                self.write(r.to_dict())
        else:
            self.render_json(f.errors, success=False, msg="")


class LogoutHandler(BaseHandler):
    @authenticated
    def get(self, *args, **kwargs):
        self.clear_session()
        self.clear_cookie("bo_current_user")
        self.clear_cookie("bo_current_user_nickname")
        self.current_user = None
        self.redirect(self.reverse_url("login"))


class VerifyUserHandler(BaseHandler):
    def get(self, *args, **kwargs):
        ak = self.get_query_argument("ak", None)
        if ak:
            s = SessionFactory.session()
            u = s.query(Users).filter(Users.user_activation_key == ak).filter(Users.user_status == 0).one_or_none()
            if u:
                u.user_activation_key = pk()
                u.user_status = 1
                s.commit()
                self.render("bocore/admin/user_verify.html")
            else:
                self.write("账号不存在或已激活，如果忘记密码请使用找回密码功能。")

    def post(self, *args, **kwargs):
        email = self.get_argument("email", None)
        if email:
            s = SessionFactory.session()
            u = s.query(Users).filter(Users.user_email == email).one_or_none()
            if u:
                rnd = Utils.random_str(8)
                try:
                    u.user_pass = Utils.md5(rnd)
                    s.commit()
                    title = "密码找回"
                    body = "新的密码: %s" % rnd
                    EMailQueue.send([u.user_email], title, body)
                    self.render_json()
                except Exception as e:
                    Logger.error(str(e))
                    self.render_json("", success=False, msg="网络错误")
            else:
                self.render_json("", success=False, msg="找不到使用此邮箱的账户")
        else:
            self.render_json({"fb_user_email": "必须填写"}, success=False, msg="")


class PluginReLoadHandler(BaseHandler):
    def post(self, *args, **kwargs):
        if self.auth({"r_manage_plugins"}, True, False):
            Hooks.load(settings.PLUGINS_ROOT, coreplugins.load)
            static_paths = Hooks.static_paths()
            for static_path in static_paths:
                urls.append(url(r"/bopress/plugin/static/{0}/(.*)".format(static_paths[static_path]),
                                StaticFileHandler,
                                {"path": os.path.join(settings.PLUGINS_ROOT, static_path)}))
            SessionFactory.create_tables()
            watch_file = os.path.join(settings.BASE_DIR, "cache", "watch.txt")
            Utils.text_write(watch_file, [Utils.uniq_index()])
            self.render_json()


@stream_request_body
class StreamHandler(BaseHandler):
    @authenticated
    def prepare(self):
        self.do_fileupload = False
        if self.auth({"r_upload_files"}, is_redirect=False, is_api=False):
            site_opts = options.get_site_options()
            if self.request.method.lower() == "post":
                size = int(site_opts.get('max_fileupload_size', default="10")) * 1024 * 1024
                self.request.connection.set_max_body_size(size)
            if self.path_args:
                upload_name = self.path_args[0]
                # Example: folder is ``/path/upload``.
                # relative_file_name is ``2017/01/02/file_name_with_no_ext`` or empty, if empty will be use uuid.
                # use_origin_file_name `True` or `False`
                folder, relative_file_name, use_origin_file_name = apply_filters("bo_fileupload", ("/tmp", "", False),
                                                                                 upload_name)
                self.upload_folder = folder
                self.relative_file_name = relative_file_name
                self.use_origin_file_name = use_origin_file_name
                if folder:
                    if self.request.method.lower() == "post":
                        if not relative_file_name:
                            created_path = Utils.format_date(Utils.current_datetime(), "/")
                            Utils.mkdirs(os.path.join(self.upload_folder, created_path))
                            self.relative_file_name = "{0}/{1}"\
                                .format(created_path, Utils.uniq_index())
                        saved_path = os.path.join(folder, self.relative_file_name)
                        self.save_file = open(saved_path, 'ab')
                    self.do_fileupload = True

    @staticmethod
    def parse_part(chunk):
        start = 0
        while True:
            try:
                i = chunk.index(b"\r\n\r\n", start)
                start = i + 4
            except Exception as e:
                Logger.nothing(e)
                break
        return chunk[start:]

    def data_received(self, chunk):
        if self.request.method.lower() == "post":
            if self.do_fileupload:
                valid_chunk = StreamHandler.parse_part(chunk)
                if valid_chunk:
                    self.save_file.write(valid_chunk)

    @authenticated
    def get(self, name):
        if self.do_fileupload and self.upload_folder:
            file_name = self.get_argument('name', '')
            file_type = self.get_argument('type', '')
            term_taxonomy_id = self.get_argument('tt_id', '')
            server_file_name = self.get_argument('file_name', '')
            file_size = int(self.get_argument('size', '0'))
            if file_name and term_taxonomy_id and server_file_name:
                target_file = os.path.join(self.upload_folder, server_file_name)
                file_ext = os.path.splitext(file_name)[1]
                target_rename_file = target_file + file_ext
                if not self.use_origin_file_name:
                    if os.path.exists(target_rename_file):
                        os.remove(target_rename_file)
                    os.rename(target_file, target_rename_file)
                else:
                    target_rename_file = os.path.join(os.path.dirname(target_rename_file), file_name)
                    if os.path.exists(target_rename_file):
                        os.remove(target_rename_file)
                    os.rename(target_file, target_rename_file)
                r = dict()
                r['file'] = os.path.relpath(target_rename_file, self.upload_folder)
                attach = Attachment()
                attach.attachment_id = pk()
                attach.origin_name = file_name
                attach.mime_type = file_type
                attach.size = file_size
                attach.save_folder = self.upload_folder
                attach.relative_path = r['file']
                s = SessionFactory.session()
                s.add(attach)
                s.commit()
                term_taxonomy.add_term_relationships(term_taxonomy_id, attach)
                r['attachment_id'] = attach.attachment_id
                self.write(r)
            else:
                r = dict()
                r['file'] = ""
                r['attachment_id'] = ""
                self.write(r)
        else:
            r = dict()
            r['file'] = ""
            r['attachment_id'] = ""
            self.write(r)

    @authenticated
    def post(self, name):
        self._do_file_data(name)

    def _do_file_data(self, name):
        if self.do_fileupload:
            try:
                self.save_file.flush()
                self.save_file.close()
                r = dict()
                r["OK"] = 1
                r["upload_name"] = name
                r["file_name"] = self.relative_file_name
                r["info"] = ""
                self.write(r)
            except Exception as e:
                Logger.exception(e)
                r = dict()
                r["OK"] = 0
                r["upload_name"] = name
                r["file_name"] = ""
                r["info"] = str(e)
                self.write(r)
        else:
            r = dict()
            r["OK"] = 0
            r["upload_name"] = name
            r["file_name"] = ""
            r["info"] = ""
            self.write(r)
        self.finish()


urls = [
    url(r'/', IndexHandler),
    url(r'/bopress/admin', AdminHandler, name="admin"),
    url(r'/bopress/api/(\w+)', ApiHandler, name="api"),
    url(r'/bopress/listtable/(\w+)', ListTableDataHandler, name="listtable"),
    url(r'/bopress/taxonomy', TaxonomyTreeHandler, name="taxonomy"),
    url(r'/bopress/login', LoginHandler, name="login"),
    url(r'/bopress/logout', LogoutHandler, name="logout"),
    url(r'/bopress/user/verify', VerifyUserHandler, name="verify"),
    url(r'/bopress/registry', RegistryHandler, name="registry"),
    url(r'/bopress/plugins_reload', PluginReLoadHandler, name="plugins_reload"),
    url(r'/bopress/upload/(\w+)', StreamHandler, name="upload")
]
